{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "nYDCtu679yz1"
   },
   "source": [
    "# PyTorch Metric Learning\n",
    "### Example for the TwoStreamMetricLoss trainer\n",
    "See the documentation [here](https://kevinmusgrave.github.io/pytorch-metric-learning/)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ChQnqzgP9tPj"
   },
   "source": [
    "## Install prereqs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 87
    },
    "id": "nLr_MM936Wd5",
    "outputId": "f8d01c9e-4943-4128-d34d-d16b76a737c2"
   },
   "outputs": [],
   "source": [
    "!pip install -q pytorch-metric-learning[with-hooks]\n",
    "!pip install umap-learn"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "PyABRGhcXrei"
   },
   "source": [
    "## Import the packages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 35
    },
    "id": "VULYLloy9ivc",
    "outputId": "09803081-2c4b-4efc-9096-ab389e9cb491"
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import logging\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torchvision\n",
    "import umap\n",
    "from cycler import cycler\n",
    "from torchvision import datasets, transforms\n",
    "\n",
    "import pytorch_metric_learning\n",
    "import pytorch_metric_learning.utils.logging_presets as logging_presets\n",
    "from pytorch_metric_learning import losses, miners, samplers, testers, trainers\n",
    "from pytorch_metric_learning.utils.accuracy_calculator import AccuracyCalculator\n",
    "\n",
    "logging.getLogger().setLevel(logging.INFO)\n",
    "logging.info(\"VERSION %s\" % pytorch_metric_learning.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "2bT2tUtC9mND"
   },
   "source": [
    "## Create two-stream dataset from CIFAR100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "VOWPA22W9lX0"
   },
   "outputs": [],
   "source": [
    "class CIFAR100TwoStreamDataset(torch.utils.data.Dataset):\n",
    "    def __init__(self, dataset, anchor_transform, posneg_transform):\n",
    "        # split by some thresholds here 80% anchors, 20% for posnegs\n",
    "        lengths = [int(len(dataset) * 0.8), int(len(dataset) * 0.2)]\n",
    "        self.anchors, self.posnegs = torch.utils.data.random_split(dataset, lengths)\n",
    "\n",
    "        self.anchor_transform = anchor_transform\n",
    "        self.posneg_transform = posneg_transform\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.anchors)\n",
    "\n",
    "    def __getitem__(self, index):\n",
    "        anchor, target = self.anchors[index]\n",
    "        if self.anchor_transform is not None:\n",
    "            anchor = self.anchor_transform(anchor)\n",
    "\n",
    "        # now pair this up with an image from the same class in the second stream\n",
    "        A = np.where(np.array(self.posnegs.dataset.targets) == target)[0]\n",
    "        posneg_idx = np.random.choice(A[np.in1d(A, self.posnegs.indices)])\n",
    "        posneg, target = self.posnegs[\n",
    "            np.where(self.posnegs.indices == posneg_idx)[0][0]\n",
    "        ]\n",
    "\n",
    "        if self.posneg_transform is not None:\n",
    "            posneg = self.posneg_transform(posneg)\n",
    "        return anchor, posneg, target"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "b79C0ZqXXyKx"
   },
   "source": [
    "##Simple model def"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "THzceJtN_p78"
   },
   "outputs": [],
   "source": [
    "class MLP(nn.Module):\n",
    "    # layer_sizes[0] is the dimension of the input\n",
    "    # layer_sizes[-1] is the dimension of the output\n",
    "    def __init__(self, layer_sizes, final_relu=False):\n",
    "        super().__init__()\n",
    "        layer_list = []\n",
    "        layer_sizes = [int(x) for x in layer_sizes]\n",
    "        num_layers = len(layer_sizes) - 1\n",
    "        final_relu_layer = num_layers if final_relu else num_layers - 1\n",
    "        for i in range(len(layer_sizes) - 1):\n",
    "            input_size = layer_sizes[i]\n",
    "            curr_size = layer_sizes[i + 1]\n",
    "            if i < final_relu_layer:\n",
    "                layer_list.append(nn.ReLU(inplace=False))\n",
    "            layer_list.append(nn.Linear(input_size, curr_size))\n",
    "        self.net = nn.Sequential(*layer_list)\n",
    "        self.last_linear = self.net[-1]\n",
    "\n",
    "    def forward(self, x):\n",
    "        return self.net(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "LTiWoweRX1T5"
   },
   "source": [
    "## Initialize models, optimizers and image transforms"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 84,
     "referenced_widgets": [
      "320b24ceac3a4c1397da8f5af7b10fa0",
      "71ad35fe9e0b41b38bd931bd9e6a3e6e",
      "4a9b66ff15064b7cbb9936fa3e32aa86",
      "1448a04dc77b4d8cba9d61fd3ca5dd2d",
      "08810712b76f401fa326f6f660e8340f",
      "63957f29539445818efcdcec5d817264",
      "3cf18e3e007e4489a983fa34bc322f0a",
      "44aec5025964427a9b5213cd3e018c16"
     ]
    },
    "id": "1A6ad-I7_smx",
    "outputId": "d62ca880-7ad6-4d06-8a11-73ac1e0b7739"
   },
   "outputs": [],
   "source": [
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "\n",
    "# Set trunk model and replace the softmax layer with an identity function\n",
    "trunk = torchvision.models.resnet18(pretrained=True)\n",
    "trunk_output_size = trunk.fc.in_features\n",
    "trunk.fc = nn.Identity()\n",
    "trunk = torch.nn.DataParallel(trunk.to(device))\n",
    "\n",
    "# Set embedder model. This takes in the output of the trunk and outputs 128 dimensional embeddings\n",
    "embedder = torch.nn.DataParallel(MLP([trunk_output_size, 128]).to(device))\n",
    "\n",
    "# Set optimizers\n",
    "trunk_optimizer = torch.optim.Adam(trunk.parameters(), lr=0.00004, weight_decay=0.00005)\n",
    "embedder_optimizer = torch.optim.Adam(\n",
    "    embedder.parameters(), lr=0.00004, weight_decay=0.00005\n",
    ")\n",
    "\n",
    "# Set the image transforms\n",
    "train_transform = transforms.Compose(\n",
    "    [\n",
    "        transforms.Resize(64),\n",
    "        transforms.RandomResizedCrop(scale=(0.16, 1), ratio=(0.75, 1.33), size=64),\n",
    "        transforms.RandomHorizontalFlip(0.5),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n",
    "    ]\n",
    ")\n",
    "\n",
    "val_transform = transforms.Compose(\n",
    "    [\n",
    "        transforms.Resize(64),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "In_aiu4BX9zF"
   },
   "source": [
    "## Initialize the datasets"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 101,
     "referenced_widgets": [
      "88c083ac7bc441478db5aaa76fe19770",
      "54033ed0e74b477a9f9e1b31d41ae3d7",
      "63a2a5304099464987b224a55b064578",
      "3334c966595a41e2b219d590706baaf9",
      "bb29e4065b74411c842e9a0f25dd7ffd",
      "e7716f5cd2df4eefa98333e1c6f1ec26",
      "237e10cf5add457491045cd7c6f6c01b",
      "b941645edd9449a1b4e6830bd7ebef43"
     ]
    },
    "id": "llktnjZJX8cN",
    "outputId": "b32d3f99-38ef-41c9-99e1-46ad6a8bfd3c"
   },
   "outputs": [],
   "source": [
    "# Download and create datasets\n",
    "original_train = datasets.CIFAR100(\n",
    "    root=\"CIFAR100_Dataset\", train=True, transform=None, download=True\n",
    ")\n",
    "original_val = datasets.CIFAR100(\n",
    "    root=\"CIFAR100_Dataset\", train=False, transform=None, download=True\n",
    ")\n",
    "\n",
    "# splits CIFAR100 into two streams\n",
    "# 20% of the images will be used as a stream for positives and negatives\n",
    "# the remaining images are used as anchor images\n",
    "\n",
    "train_dataset = CIFAR100TwoStreamDataset(\n",
    "    original_train, anchor_transform=train_transform, posneg_transform=train_transform\n",
    ")\n",
    "val_dataset = CIFAR100TwoStreamDataset(\n",
    "    original_val, anchor_transform=val_transform, posneg_transform=val_transform\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "SG5rnOxHYGQ_"
   },
   "source": [
    "## Create the loss, miner, sampler, and package them into dictionaries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "B9Q7B0OKXkil"
   },
   "outputs": [],
   "source": [
    "# Set the loss function\n",
    "loss = losses.TripletMarginLoss(margin=0.2)\n",
    "\n",
    "# Set the mining function\n",
    "miner = miners.TripletMarginMiner(margin=0.2)\n",
    "\n",
    "# Set the dataloader sampler\n",
    "sampler = samplers.MPerClassSampler(\n",
    "    original_train.classes, m=1, length_before_new_iter=len(train_dataset)\n",
    ")\n",
    "\n",
    "# Set other training parameters\n",
    "batch_size = 128\n",
    "num_epochs = 4\n",
    "\n",
    "# Package the above stuff into dictionaries.\n",
    "models = {\"trunk\": trunk, \"embedder\": embedder}\n",
    "optimizers = {\n",
    "    \"trunk_optimizer\": trunk_optimizer,\n",
    "    \"embedder_optimizer\": embedder_optimizer,\n",
    "}\n",
    "loss_funcs = {\"metric_loss\": loss}\n",
    "mining_funcs = {\"tuple_miner\": miner}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "5wPolfqrXZ4I"
   },
   "outputs": [],
   "source": [
    "# Remove logs if you want to train with new parameters\n",
    "!rm -rf example_logs/ example_saved_models/ example_tensorboard/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "BLyl-1i_YJpJ"
   },
   "source": [
    "## Create the training and testing hooks"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "i4qQJLET_0iB"
   },
   "outputs": [],
   "source": [
    "record_keeper, _, _ = logging_presets.get_record_keeper(\n",
    "    \"example_logs\", \"example_tensorboard\"\n",
    ")\n",
    "hooks = logging_presets.get_hook_container(record_keeper)\n",
    "dataset_dict = {\"val\": val_dataset}\n",
    "model_folder = \"example_saved_models\"\n",
    "\n",
    "\n",
    "def visualizer_hook(umapper, umap_embeddings, labels, split_name, keyname, *args):\n",
    "    logging.info(\n",
    "        \"UMAP plot for the {} split and label set {}\".format(split_name, keyname)\n",
    "    )\n",
    "    label_set = np.unique(labels)\n",
    "    num_classes = len(label_set)\n",
    "    plt.figure(figsize=(20, 15))\n",
    "    plt.gca().set_prop_cycle(\n",
    "        cycler(\n",
    "            \"color\", [plt.cm.nipy_spectral(i) for i in np.linspace(0, 0.9, num_classes)]\n",
    "        )\n",
    "    )\n",
    "    half = int(umap_embeddings.shape[0] / 2)\n",
    "    anchors = umap_embeddings[:half]\n",
    "    posneg = umap_embeddings[half:]\n",
    "    labels = labels[:half]\n",
    "    for i in range(num_classes):\n",
    "        idx = labels == label_set[i]\n",
    "        plt.plot(posneg[idx, 0], posneg[idx, 1], \"s\", markersize=1)\n",
    "        plt.plot(anchors[idx, 0], anchors[idx, 1], \".\", markersize=1)\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "# Create the tester\n",
    "tester = testers.GlobalTwoStreamEmbeddingSpaceTester(\n",
    "    end_of_testing_hook=hooks.end_of_testing_hook,\n",
    "    visualizer=umap.UMAP(n_neighbors=50),\n",
    "    visualizer_hook=visualizer_hook,\n",
    "    dataloader_num_workers=2,\n",
    "    accuracy_calculator=AccuracyCalculator(k=\"max_bin_count\"),\n",
    ")\n",
    "\n",
    "end_of_epoch_hook = hooks.end_of_epoch_hook(tester, dataset_dict, model_folder)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "8Fu2GPXoYSkA"
   },
   "source": [
    "## Create the trainer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "R4IYl6dEYQFi"
   },
   "outputs": [],
   "source": [
    "trainer = trainers.TwoStreamMetricLoss(\n",
    "    models,\n",
    "    optimizers,\n",
    "    batch_size,\n",
    "    loss_funcs,\n",
    "    train_dataset,\n",
    "    mining_funcs=mining_funcs,\n",
    "    sampler=sampler,\n",
    "    dataloader_num_workers=2,\n",
    "    end_of_iteration_hook=hooks.end_of_iteration_hook,\n",
    "    end_of_epoch_hook=end_of_epoch_hook,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "LCjFfzIgODQ0"
   },
   "source": [
    "## Start Tensorboard\n",
    "(Turn off adblock and other shields)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "J7JvXuskODXY"
   },
   "outputs": [],
   "source": [
    "%load_ext tensorboard\n",
    "%tensorboard --logdir example_tensorboard"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "I20LpzvEYT_y"
   },
   "source": [
    "## Train the model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 1000
    },
    "id": "_Zs2-DveYQfW",
    "outputId": "48a9c7a8-4926-47a7-e6fd-066e8dbe1622"
   },
   "outputs": [],
   "source": [
    "# In the embeddings plots, the small dots represent the 1st stream, and the larger dots represent the 2nd stream\n",
    "trainer.train(num_epochs=num_epochs)"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "collapsed_sections": [],
   "name": "TwoStreamMetricLossOnly.ipynb",
   "provenance": [],
   "toc_visible": true
  },
  "kernelspec": {
   "display_name": "Python 3.8.10 64-bit",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  },
  "vscode": {
   "interpreter": {
    "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
   }
  },
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "08810712b76f401fa326f6f660e8340f": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "ProgressStyleModel",
     "state": {
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "ProgressStyleModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "StyleView",
      "bar_color": null,
      "description_width": "initial"
     }
    },
    "1448a04dc77b4d8cba9d61fd3ca5dd2d": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "HTMLModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "HTMLModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "HTMLView",
      "description": "",
      "description_tooltip": null,
      "layout": "IPY_MODEL_44aec5025964427a9b5213cd3e018c16",
      "placeholder": "​",
      "style": "IPY_MODEL_3cf18e3e007e4489a983fa34bc322f0a",
      "value": " 44.7M/44.7M [00:00&lt;00:00, 147MB/s]"
     }
    },
    "237e10cf5add457491045cd7c6f6c01b": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "DescriptionStyleModel",
     "state": {
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "DescriptionStyleModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "StyleView",
      "description_width": ""
     }
    },
    "320b24ceac3a4c1397da8f5af7b10fa0": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "HBoxModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "HBoxModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "HBoxView",
      "box_style": "",
      "children": [
       "IPY_MODEL_4a9b66ff15064b7cbb9936fa3e32aa86",
       "IPY_MODEL_1448a04dc77b4d8cba9d61fd3ca5dd2d"
      ],
      "layout": "IPY_MODEL_71ad35fe9e0b41b38bd931bd9e6a3e6e"
     }
    },
    "3334c966595a41e2b219d590706baaf9": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "HTMLModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "HTMLModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "HTMLView",
      "description": "",
      "description_tooltip": null,
      "layout": "IPY_MODEL_b941645edd9449a1b4e6830bd7ebef43",
      "placeholder": "​",
      "style": "IPY_MODEL_237e10cf5add457491045cd7c6f6c01b",
      "value": " 169009152/? [00:20&lt;00:00, 32340770.95it/s]"
     }
    },
    "3cf18e3e007e4489a983fa34bc322f0a": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "DescriptionStyleModel",
     "state": {
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "DescriptionStyleModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "StyleView",
      "description_width": ""
     }
    },
    "44aec5025964427a9b5213cd3e018c16": {
     "model_module": "@jupyter-widgets/base",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "4a9b66ff15064b7cbb9936fa3e32aa86": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "IntProgressModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "IntProgressModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "ProgressView",
      "bar_style": "success",
      "description": "100%",
      "description_tooltip": null,
      "layout": "IPY_MODEL_63957f29539445818efcdcec5d817264",
      "max": 46827520,
      "min": 0,
      "orientation": "horizontal",
      "style": "IPY_MODEL_08810712b76f401fa326f6f660e8340f",
      "value": 46827520
     }
    },
    "54033ed0e74b477a9f9e1b31d41ae3d7": {
     "model_module": "@jupyter-widgets/base",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "63957f29539445818efcdcec5d817264": {
     "model_module": "@jupyter-widgets/base",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "63a2a5304099464987b224a55b064578": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "IntProgressModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "IntProgressModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "ProgressView",
      "bar_style": "info",
      "description": "",
      "description_tooltip": null,
      "layout": "IPY_MODEL_e7716f5cd2df4eefa98333e1c6f1ec26",
      "max": 1,
      "min": 0,
      "orientation": "horizontal",
      "style": "IPY_MODEL_bb29e4065b74411c842e9a0f25dd7ffd",
      "value": 1
     }
    },
    "71ad35fe9e0b41b38bd931bd9e6a3e6e": {
     "model_module": "@jupyter-widgets/base",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "88c083ac7bc441478db5aaa76fe19770": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "HBoxModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "HBoxModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "HBoxView",
      "box_style": "",
      "children": [
       "IPY_MODEL_63a2a5304099464987b224a55b064578",
       "IPY_MODEL_3334c966595a41e2b219d590706baaf9"
      ],
      "layout": "IPY_MODEL_54033ed0e74b477a9f9e1b31d41ae3d7"
     }
    },
    "b941645edd9449a1b4e6830bd7ebef43": {
     "model_module": "@jupyter-widgets/base",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "bb29e4065b74411c842e9a0f25dd7ffd": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "ProgressStyleModel",
     "state": {
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "ProgressStyleModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "StyleView",
      "bar_color": null,
      "description_width": "initial"
     }
    },
    "e7716f5cd2df4eefa98333e1c6f1ec26": {
     "model_module": "@jupyter-widgets/base",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    }
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
